﻿#include "ssMySQLStmt.h"
#include "ssMySQLConnection.h"
#include "string/ssStrfunc.h"
#include <sstream>


namespace StarSeeker
{

ssMySQLStmt::ssMySQLStmt()
{
	m_pConnect = 0;
	m_hStmt = 0;
	m_resFlag = 0;
	m_numIn = 0;
	m_numOut = 0;
	m_in = 0;
	m_out = 0;
	m_inParam = 0;
	m_outParam = 0;
	m_state = 0;
}

ssMySQLStmt::~ssMySQLStmt()
{
	Clear();
}

void ssMySQLStmt::Init(ssMySQLConnection * pConn)
{
	m_pConnect = pConn;
	m_hStmt = mysql_stmt_init(m_pConnect->m_pConnect);
	if (!m_hStmt)
	{
		CheckError(0);
		return;
	}
	m_state = 1;
}

void ssMySQLStmt::Clear()
{
	if (m_hStmt)
	{
		mysql_stmt_free_result(m_hStmt);
		mysql_stmt_close(m_hStmt);
		m_hStmt = 0;
	}
	for (u8 i=0; i<m_numIn && m_inParam; ++i)
	{
		SS_SAFE_DEL_ARRAY(m_in)
		SS_SAFE_DELETE(m_inParam[i])
	}
	SS_SAFE_DEL_ARRAY(m_inParam)
	for (u8 i=0; i<m_numOut && m_outParam; ++i)
	{
		SS_SAFE_DEL_ARRAY(m_out)
		SS_SAFE_DELETE(m_outParam[i])
	}
	SS_SAFE_DEL_ARRAY(m_outParam)
	m_state = 0;
	m_numIn = 0;
	m_numOut = 0;
}

int ssMySQLStmt::PrepareQuery(const char* pIn, const char* pOut, const char* pSql, const u8 resultFlag)
{
	if (m_in || m_out)
		return DB_QUERY_PARAM_ERROR;
	m_sql = pSql;
	m_numIn = 0;
	m_numOut = 0;
	std::vector<std::string> strInParam, strOutParam;
	if (pIn)
	{
		std::string strIn(pIn);
		StringSplit(strIn, ',', strInParam);
		m_numIn = (u32)strInParam.size();
	}
	if (pOut)
	{
		std::string strOut(pOut);
		StringSplit(strOut, ',', strOutParam);
		m_numOut = (u32)strOutParam.size();
	}
	if (m_numIn > DB_PARAM_FLAG_NUM || m_numOut > DB_PARAM_FLAG_NUM)
	{
		m_pConnect->m_error = "mysql param num error!";
		return DB_QUERY_PARAM_ERROR;
	}
	m_resFlag = resultFlag;
	//输入参数
	if (m_numIn>0)
	{
		m_in = new MYSQL_BIND[m_numIn];
		memset(m_in, 0, sizeof(MYSQL_BIND)*m_numIn);
		m_inParam = new QueryParam_s*[m_numIn];
		for (u32 i=0; i<m_numIn; ++i)
		{
			if (strInParam[i]=="i4")
			{
				m_inParam[i] = new QueryInt_s;
				m_inParam[i]->Bind(m_in[i], 0);
			}
			else if (strInParam[i] == "u4")
			{
				m_inParam[i] = new QueryUInt_s;
				m_inParam[i]->Bind(m_in[i], 0);
			}
			else if (strInParam[i] == "i1")
			{
				m_inParam[i] = new QueryChar_s;
				m_inParam[i]->Bind(m_in[i], 0);
			}
			else if (strInParam[i] == "u1")
			{
				m_inParam[i] = new QueryUChar_s;
				m_inParam[i]->Bind(m_in[i], 0);
			}
			else if (strInParam[i] == "i2")
			{
				m_inParam[i] = new QueryShort_s;
				m_inParam[i]->Bind(m_in[i], 0);
			}
			else if (strInParam[i] == "u2")
			{
				m_inParam[i] = new QueryUShort_s;
				m_inParam[i]->Bind(m_in[i], 0);
			}
			else if (strInParam[i] == "i8")
			{
				m_inParam[i] = new QueryInt64_s;
				m_inParam[i]->Bind(m_in[i], 0);
			}
			else if (strInParam[i] == "u8")
			{
				m_inParam[i] = new QueryUInt64_s;
				m_inParam[i]->Bind(m_in[i], 0);
			}
			else if (strInParam[i] == "dt")
			{
				m_inParam[i] = new QueryDateTime_s;
				m_inParam[i]->Bind(m_in[i], 0);
			}
			else if (strInParam[i][0] == 's')
			{
				m_inParam[i] = new QueryString_s;
				unsigned int len = Str2UInt32(strInParam[i].c_str()+1);
				m_inParam[i]->Bind(m_in[i], len);
			}
			else if (strInParam[i][0] == 'b')
			{
				m_inParam[i] = new QueryBuffer_s;
				unsigned int len = Str2UInt32(strInParam[i].c_str() + 1);
				m_inParam[i]->Bind(m_in[i], len);
			}
			else if (strInParam[i] == "f4")
			{
				m_inParam[i] = new QueryFloat_s;
				m_inParam[i]->Bind(m_in[i], 0);
			}
			else
			{
				m_pConnect->m_error = "PrepareQuery:mysql in-param flag error!";
				return DB_QUERY_PARAM_ERROR;
			}
		}
	}
	//输出参数
	if (m_numOut>0)
	{
		m_out = new MYSQL_BIND[m_numOut];
		memset(m_out, 0, sizeof(MYSQL_BIND)*m_numOut);
		m_outParam = new QueryParam_s*[m_numOut];
		for (u32 i = 0; i < m_numOut; ++i)
		{
			if (strOutParam[i] == "i4")
			{
				m_outParam[i] = new QueryInt_s;
				m_outParam[i]->Bind(m_out[i], 0);
			}
			else if (strOutParam[i] == "u4")
			{
				m_outParam[i] = new QueryUInt_s;
				m_outParam[i]->Bind(m_out[i], 0);
			}
			else if (strOutParam[i] == "i1")
			{
				m_outParam[i] = new QueryChar_s;
				m_outParam[i]->Bind(m_out[i], 0);
			}
			else if (strOutParam[i] == "u1")
			{
				m_outParam[i] = new QueryUChar_s;
				m_outParam[i]->Bind(m_out[i], 0);
			}
			else if (strOutParam[i] == "i2")
			{
				m_outParam[i] = new QueryShort_s;
				m_outParam[i]->Bind(m_out[i], 0);
			}
			else if (strOutParam[i] == "u2")
			{
				m_outParam[i] = new QueryUShort_s;
				m_outParam[i]->Bind(m_out[i], 0);
			}
			else if (strOutParam[i] == "i8")
			{
				m_outParam[i] = new QueryInt64_s;
				m_outParam[i]->Bind(m_out[i], 0);
			}
			else if (strOutParam[i] == "u8")
			{
				m_outParam[i] = new QueryUInt64_s;
				m_outParam[i]->Bind(m_out[i], 0);
			}
			else if (strOutParam[i] == "dt")
			{
				m_outParam[i] = new QueryDateTime_s;
				m_outParam[i]->Bind(m_out[i], 0);
			}
			else if (strOutParam[i][0] == 's')
			{
				m_outParam[i] = new QueryString_s;
				unsigned int len = Str2UInt32(strOutParam[i].c_str() + 1);
				m_outParam[i]->Bind(m_out[i], len);
			}
			else if (strOutParam[i][0] == 'b')
			{
				m_outParam[i] = new QueryBuffer_s;
				unsigned int len = Str2UInt32(strOutParam[i].c_str() + 1);
				m_outParam[i]->Bind(m_out[i], len);
			}
			else if (strOutParam[i] == "f4")
			{
				m_outParam[i] = new QueryFloat_s;
				m_outParam[i]->Bind(m_out[i], 0);
			}
			else
			{
				m_pConnect->m_error = "PrepareQuery:mysql out-param flag error!";
				return DB_QUERY_PARAM_ERROR;
			}
		}
	}
	return Prepare();
}

int ssMySQLStmt::ExecuteQueryVariable(i32 num, ...)
{
	int ret = 0;
	va_list arglist;
	va_start(arglist, num);
	ret = ExecuteQueryVariable(num, arglist);
	va_end(arglist);
	return ret;
}

int ssMySQLStmt::ExecuteQueryVariable(i32 num, va_list& arglist)
{
	if ((u32)num < m_numIn)
		return DB_QUERY_PARAM_ERROR;
	//参数赋值
	ResetParam();
	if (m_numIn > 0)
	{
		for (u32 i = 0; i < m_numIn; ++i)
		{
			switch (m_inParam[i]->type)
			{
			case '1':
			{
				int x = va_arg(arglist, int);
				if (!m_inParam[i]->SetValue(&x, 0))
				{
					m_pConnect->m_error = "mysql in-param error 1";
					return DB_QUERY_PARAM_ERROR;
				}
				if (--num < 0)
				{
					m_pConnect->m_error = "mysql in-param num error 1";
					return DB_QUERY_PARAM_ERROR;
				}
			}
			break;
			case '2':
			{
				unsigned int x = va_arg(arglist, unsigned int);
				if (!m_inParam[i]->SetValue(&x, 0))
				{
					m_pConnect->m_error = "mysql in-param error 2";
					return DB_QUERY_PARAM_ERROR;
				}
				if (--num < 0)
				{
					m_pConnect->m_error = "mysql in-param num error 2";
					return DB_QUERY_PARAM_ERROR;
				}
			}
			break;
			case '3':
			{
				char x = va_arg(arglist, int);
				if (!m_inParam[i]->SetValue(&x, 0))
				{
					m_pConnect->m_error = "mysql in-param error 3";
					return DB_QUERY_PARAM_ERROR;
				}
				if (--num < 0)
				{
					m_pConnect->m_error = "mysql in-param num error 3";
					return DB_QUERY_PARAM_ERROR;
				}
			}
			break;
			case '4':
			{
				unsigned char x = va_arg(arglist, unsigned int);
				if (!m_inParam[i]->SetValue(&x, 0))
				{
					m_pConnect->m_error = "mysql in-param error 4";
					return DB_QUERY_PARAM_ERROR;
				}
				if (--num < 0)
				{
					m_pConnect->m_error = "mysql in-param num error 4!";
					return DB_QUERY_PARAM_ERROR;
				}
			}
			break;
			case '5':
			{
				short x = va_arg(arglist, int);
				if (!m_inParam[i]->SetValue(&x, 0))
				{
					m_pConnect->m_error = "mysql in-param error 5!";
					return DB_QUERY_PARAM_ERROR;
				}
				if (--num < 0)
				{
					m_pConnect->m_error = "mysql in-param num error 5!";
					return DB_QUERY_PARAM_ERROR;
				}
			}
			break;
			case '6':
			{
				unsigned short x = va_arg(arglist, unsigned int);
				if (!m_inParam[i]->SetValue(&x, 0))
				{
					m_pConnect->m_error = "mysql in-param error 6!";
					return DB_QUERY_PARAM_ERROR;
				}
				if (--num < 0)
				{
					m_pConnect->m_error = "mysql in-param num error 6!";
					return DB_QUERY_PARAM_ERROR;
				}
			}
			break;
			case '7':
			{
				long long x = va_arg(arglist, long long);
				if (!m_inParam[i]->SetValue(&x, 0))
				{
					m_pConnect->m_error = "mysql in-param error 7!";
					return DB_QUERY_PARAM_ERROR;
				}
				if (--num < 0)
				{
					m_pConnect->m_error = "mysql in-param num error 7!";
					return DB_QUERY_PARAM_ERROR;
				}
			}
			break;
			case '8':
			{
				unsigned long long x = va_arg(arglist, unsigned long long);
				if (!m_inParam[i]->SetValue(&x, 0))
				{
					m_pConnect->m_error = "mysql in-param error 8!";
					return DB_QUERY_PARAM_ERROR;
				}
				if (--num < 0)
				{
					m_pConnect->m_error = "mysql in-param num error 8!";
					return DB_QUERY_PARAM_ERROR;
				}
			}
			break;
			case '9':
			{
				{
					QueryDateTime_s* pTime = SS_DYNAMIC_CAST(QueryDateTime_s*, m_inParam[i]);
					ssYMDHMS* pParam = SS_REINTERPRET_CAST(ssYMDHMS*, va_arg(arglist, void*));
					pTime->SetValue(pParam->year.u32p, pParam->month.u32p, pParam->day.u32p, pParam->hour.u32p, pParam->minute.u32p, pParam->second.u32p);
				}
				if (--num < 0)
				{
					m_pConnect->m_error = "mysql in-param num error 9!";
					return DB_QUERY_PARAM_ERROR;
				}
			}
			break;
			case 'a':
			case 'b':
			{
				unsigned int xlen = va_arg(arglist, unsigned int);
				char* px = va_arg(arglist, char*);
				if (!m_inParam[i]->SetValue(px, xlen))
				{
					m_pConnect->m_error = "mysql in-param error a-b!";
					return DB_QUERY_PARAM_ERROR;
				}
				num -= 2;
				if (num < 0)
				{
					m_pConnect->m_error = "mysql in-param num error a-b!";
					return DB_QUERY_PARAM_ERROR;
				}
			}
			break;
			case 'c':
			{
				float x = va_arg(arglist, double);
				if (!m_inParam[i]->SetValue(&x, 0))
				{
					m_pConnect->m_error = "mysql in-param error c!";
					return DB_QUERY_PARAM_ERROR;
				}
				if (--num < 0)
				{
					m_pConnect->m_error = "mysql in-param num error c!";
					return DB_QUERY_PARAM_ERROR;
				}
			}
			break;
			default:
				m_pConnect->m_error = "mysql in-param type error!";
				return DB_QUERY_PARAM_ERROR;
			}
		}
	}
	int ret = 0;
	if (m_numIn > 0)
	{
		ret = ExecuteSQL(m_in);
	}
	else
	{
		ret = ExecuteSQL(0);
	}
	return ret;
}

int ssMySQLStmt::ExecuteQueryStream(u32 len, const char* data)
{
	char* pData = SS_CONST_CAST(char*, data);
	u32 dataLen = len;
	//参数赋值
	ResetParam();
	if (m_numIn>0)
	{
		for (u32 i=0; i<m_numIn; ++i)
		{
			switch (m_inParam[i]->type)
			{
			case '1':
				m_inParam[i]->SetValue(pData, 0);
				pData += cg_i32_size;
				if (dataLen<cg_i32_size)
				{
					m_pConnect->m_error = "mysql in-param len error 1!";
					return DB_QUERY_PARAM_ERROR;
				}
				dataLen -= cg_i32_size;
				break;
			case '2':
				m_inParam[i]->SetValue(pData, 0);
				pData += cg_u32_size;
				if (dataLen<cg_u32_size)
				{
					m_pConnect->m_error = "mysql in-param len error 2!";
					return DB_QUERY_PARAM_ERROR;
				}
				dataLen -= cg_u32_size;
				break;
			case '3':
				m_inParam[i]->SetValue(pData, 0);
				pData += cg_i8_size;
				if (dataLen<cg_i8_size)
				{
					m_pConnect->m_error = "mysql in-param len error 3!";
					return DB_QUERY_PARAM_ERROR;
				}
				dataLen -= cg_i8_size;
				break;
			case '4':
				m_inParam[i]->SetValue(pData, 0);
				pData += cg_u8_size;
				if (dataLen<cg_u8_size)
				{
					m_pConnect->m_error = "mysql in-param len error 4!";
					return DB_QUERY_PARAM_ERROR;
				}
				dataLen -= cg_u8_size;
				break;
			case '5':
				m_inParam[i]->SetValue(pData, 0);
				pData += cg_i16_size;
				if (dataLen < cg_i16_size)
				{
					m_pConnect->m_error = "mysql in-param len error 5!";
					return DB_QUERY_PARAM_ERROR;
				}
				dataLen -= cg_i16_size;
				break;
			case '6':
				m_inParam[i]->SetValue(pData, 0);
				pData += cg_u16_size;
				if (dataLen < cg_u16_size)
				{
					m_pConnect->m_error = "mysql in-param len error 6!";
					return DB_QUERY_PARAM_ERROR;
				}
				dataLen -= cg_u16_size;
				break;
			case '7':
				m_inParam[i]->SetValue(pData, 0);
				pData += cg_i64_size;
				if (dataLen < cg_i64_size)
				{
					m_pConnect->m_error = "mysql in-param len error 7!";
					return DB_QUERY_PARAM_ERROR;
				}
				dataLen -= cg_i64_size;
				break;
			case '8':
				m_inParam[i]->SetValue(pData, 0);
				pData += cg_u64_size;
				if (dataLen < cg_u64_size)
				{
					m_pConnect->m_error = "mysql in-param len error 8!";
					return DB_QUERY_PARAM_ERROR;
				}
				dataLen -= cg_u64_size;
				break;
			case '9':
				{
					QueryDateTime_s* pTime = SS_DYNAMIC_CAST(QueryDateTime_s*, m_inParam[i]);
					ssYMDHMS* pParam = SS_REINTERPRET_CAST(ssYMDHMS*, pData);
					if (dataLen < sizeof(ssYMDHMS))
					{
						m_pConnect->m_error = "mysql in-param len error 9!";
						return DB_QUERY_PARAM_ERROR;
					}
					pTime->SetValue(pParam->year.u32p, pParam->month.u32p, pParam->day.u32p, pParam->hour.u32p, pParam->minute.u32p, pParam->second.u32p);
					pData += sizeof(ssYMDHMS);
					dataLen -= sizeof(ssYMDHMS);
				}
				break;
			case 'a':
			case 'b':
				{
					u32 xlen = *SS_REINTERPRET_CAST(u32*, pData);
					pData += cg_u32_size;
					if (!m_inParam[i]->SetValue(pData, xlen))
					{
						m_pConnect->m_error = "mysql in-param error a-b!";
						return DB_QUERY_PARAM_ERROR;
					}
					pData += xlen;
					if (dataLen<xlen + cg_u32_size)
					{
						m_pConnect->m_error = "mysql in-param len error a-b!";
						return DB_QUERY_PARAM_ERROR;
					}
					dataLen -= xlen + cg_u32_size;
				}
				break;
			case 'c':
				m_inParam[i]->SetValue(pData, 0);
				pData += cg_f32_size;
				if (dataLen < cg_f32_size)
				{
					m_pConnect->m_error = "mysql in-param len error c!";
					return DB_QUERY_PARAM_ERROR;
				}
				dataLen -= cg_f32_size;
				break;
			default:
				m_pConnect->m_error = "mysql in-param type error!";
				return DB_QUERY_PARAM_ERROR;
			}
		}
	}
	int ret = 0;
	if (m_numIn>0)
	{
		ret = ExecuteSQL(m_in);
	}
	else
	{
		ret = ExecuteSQL(0);
	}
	return ret;
}

void ssMySQLStmt::ClearResult()
{
	if (m_hStmt)
	{
		mysql_stmt_free_result(m_hStmt);
	}
	m_pConnect->ClearResult();
}

void ssMySQLStmt::CheckError(int ret)
{
	if (ret)
	{
		if (ret == MYSQL_NO_DATA)
		{
			m_pConnect->m_error = "mysql_stmt_fetch error: MYSQL_NO_DATA";
		}
		else if (ret == MYSQL_DATA_TRUNCATED)
		{
			m_pConnect->m_error = "mysql_stmt_fetch error: MYSQL_DATA_TRUNCATED";
		}
	} 
	else
	{
		unsigned int err = StmtErrno();
		std::ostringstream s;
		s << "[" << m_sql << "]error:" << err << ":" << StmtError();
		m_pConnect->m_error = s.str();
		//if (err==0 || err==2013 || err==2006 || err==2014)
		//{
			//m_pMain->Reconnect();
		//}
	}
}

int ssMySQLStmt::ExecuteSQL(MYSQL_BIND *pin)
{
	int rt = 0;
	if (pin)
	{
		rt = mysql_stmt_bind_param(m_hStmt, pin);
		if (0!=rt)
		{
			CheckError(0);
			return DB_QUERY_ERROR;
		}
	}
	rt = mysql_stmt_execute(m_hStmt);
	if (0!=rt)
	{
		CheckError(0);
		return DB_QUERY_ERROR;
	}
	m_pConnect->m_insertID = 0;
	m_pConnect->m_affectedRows = mysql_stmt_affected_rows(m_hStmt);
	if (m_resFlag & DB_RESULT_INSERT_ID)
	{
		m_pConnect->m_insertID = mysql_stmt_insert_id(m_hStmt);
	}
	return rt;
}

int ssMySQLStmt::GetSQLResult( MYSQL_BIND *pout )
{
	int rt = mysql_stmt_bind_result(m_hStmt, pout);
	if (0!=rt)
	{
		CheckError(0);
		return rt;
	}
	rt = mysql_stmt_store_result(m_hStmt);
	if (0!=rt)
	{
		CheckError(0);
	}
	return rt;
}

void ssMySQLStmt::ResetParam()
{
	for (u32 i=0; i<m_numOut; ++i)
		m_outParam[i]->Reset();
	for (u32 i=0; i<m_numIn; ++i)
		m_inParam[i]->Reset();
}

i64 ssMySQLStmt::FormatResult()
{
	i64 row = 0;
	m_pConnect->m_result.Empty();
	m_pConnect->m_resultSet.Empty();
	if (GetSQLResult(m_out) != 0)
	{
		return DB_QUERY_RESULE_ERROR;
	}
	if ((m_resFlag & DB_RESULT_ONE) || (m_resFlag & DB_RESULT_MULTI))
	{
		row = (i64)mysql_stmt_num_rows(m_hStmt);
		if (row>0)
		{
			int err = 0;
			for (i64 j=0; j<row; ++j)
			{
				err = mysql_stmt_fetch(m_hStmt);
				if (0!=err)
				{
					row = DB_QUERY_RESULE_ERROR;
					CheckError(err);
					break;
				}
				for (u32 i=0; i<m_numOut; ++i)
				{
					switch (m_outParam[i]->type)
					{
					case '1':
						m_pConnect->m_result.WriteData(m_outParam[i]->GetValueMemory(), cg_i32_size);
						break;
					case '2':
						m_pConnect->m_result.WriteData(m_outParam[i]->GetValueMemory(), cg_u32_size);
						break;
					case '3':
						m_pConnect->m_result.WriteData(m_outParam[i]->GetValueMemory(), cg_i8_size);
						break;
					case '4':
						m_pConnect->m_result.WriteData(m_outParam[i]->GetValueMemory(), cg_u8_size);
						break;
					case '5':
						m_pConnect->m_result.WriteData(m_outParam[i]->GetValueMemory(), cg_i16_size);
						break;
					case '6':
						m_pConnect->m_result.WriteData(m_outParam[i]->GetValueMemory(), cg_u16_size);
						break;
					case '7':
						m_pConnect->m_result.WriteData(m_outParam[i]->GetValueMemory(), cg_i64_size);
						break;
					case '8':
						m_pConnect->m_result.WriteData(m_outParam[i]->GetValueMemory(), cg_u64_size);
						break;
					case '9':
						{
							QueryDateTime_s* pTime = SS_DYNAMIC_CAST(QueryDateTime_s*, m_outParam[i]);
							ssYMDHMS ty;
							pTime->GetValue(ty);
							m_pConnect->m_result.WriteData(&ty, sizeof(ssYMDHMS));
						}
						break;
					case 'a':
					case 'b':
						m_pConnect->m_result.WriteStruct(m_outParam[i]->GetValueMemory(), m_outParam[i]->len);
						break;
					case 'c':
						m_pConnect->m_result.WriteData(m_outParam[i]->GetValueMemory(), cg_f32_size);
						break;
					default:
						m_pConnect->m_error = "FormatResult:mysql out-param type error!";
						row = DB_QUERY_RESULE_ERROR;
						return row;
					}
					m_outParam[i]->Reset();
				}
				m_pConnect->m_resultSet.PushBack(m_pConnect->m_result.Buf(), m_pConnect->m_result.Size());
				if (m_resFlag & DB_RESULT_ONE)
				{
					break;
				}
				m_pConnect->m_result.Empty();
			}
		}
	}
	ClearResult();
	return row;
}

int ssMySQLStmt::Prepare()
{
	if (!m_hStmt || m_sql.empty())
	{
		return 0;
	}
	int rt = mysql_stmt_prepare(m_hStmt, m_sql.c_str(), m_sql.size());
	if (0 != rt)
	{
		CheckError(0);
		return DB_PREPARE_ERROR;
	}
	m_state = 2;
	return 0;
}

}
